#pragma once

typedef enum {
  TOK_INTEGER, 
  TOK_COLON, 
  TOK_STRING, 
  TOK_WORD,
  TOK_LBRACKET,
  TOK_RBRACKET,
  TOK_EOS
} TOK;

typedef struct NString {
  int size;
  char* data;
} NString;

typedef union {
  int integer;
  NString string;
} TokenValue;

typedef struct Token {
  TOK type;
  TokenValue value;
} Token;

typedef struct Lexer {
  int line;
  Token token;
  Token next;
  char* base;
  char* p;
} Lexer;

typedef enum {
  WT_BUILTIN,
  WT_SIKLE
} WORD_TYPE;

typedef struct Word {
  WORD_TYPE type;
  NString name;
  void* entry;
  int count;
} Word;

typedef struct ProgramStack {
  int size;
  Token* bp;
  Token* sp;
} ProgramStack;

typedef struct ExecStack {
  int size;
  unsigned char* bp;
  unsigned char* sp;
} ExecStack;


/* hashtable.c */
typedef struct HashTable HashTable;
typedef struct HashNode HashNode;

typedef struct SikleState {
  ProgramStack* program_stack;
  ProgramStack* uname_program_stack;
  ExecStack* exec_stack;
  HashTable* word_table;
  Lexer* lexer;
} SikleState;

typedef void (*builtin_word_func)(SikleState* ss);

/* lex.c */
void next_token(Lexer* lex);
void peek(Lexer* lex);
void init_lexer(Lexer* lexer, char* filename);

/* nstring.c */
NString NString_new(char* str, int size);
int NString_equal(NString ns, NString nd);
int NString_hash(NString ns);

HashTable* HashTable_new();
void HashTable_put_word(HashTable* table, Word word);
Word* HashTable_get_word(HashTable* table, NString name);

/* stack_op.c */
ProgramStack* ProgramStack_new(int size);
ExecStack* ExecStack_new(int size);

#define LEX(ss) ((ss)->lexer)
#define PS(ss) ((ss)->program_stack)
#define UPS(ss) ((ss)->uname_program_stack)
#define ES(ss) ((ss)->exec_stack)
#define WT(ss) ((ss)->word_table)

#define ss_tok(ss) ((ss)->lexer->token)
#define ss_tok_type(ss) (ss_tok(ss).type)
#define ss_integer(ss) (ss_tok(ss).value.integer)
#define ss_string(ss) (ss_tok(ss).value.string)

#define tok_integer(tok) ((tok).value.integer)
#define tok_string(tok) ((tok).value.string)

static inline 
int peek_char(SikleState* ss)
{
  char ch;

  ch = *(char*)ES(ss)->sp;

  return ch;
}

static inline 
void push_char(SikleState* ss, char ch)
{
  *(char*)ES(ss)->sp = ch;
  ES(ss)->sp += sizeof(char);
}

static inline
char pop_char(SikleState* ss)
{
  char ch;
  
  ES(ss)->sp -= sizeof(char);
  ch = *(char*)ES(ss)->sp;

  return ch;
}

static inline 
int peek_integer(SikleState* ss)
{
  int integer;

  integer = *(int*)(ES(ss)->sp - sizeof(int));

  return integer;
}

static inline 
void push_integer(SikleState* ss, int integer)
{
  *(int*)ES(ss)->sp = integer;
  ES(ss)->sp += sizeof(int);
}

static inline
int pop_integer(SikleState* ss)
{
  int integer;

  ES(ss)->sp -= sizeof(int);
  integer = *(int*)ES(ss)->sp;

  return integer;
}

static inline 
void* peek_ptr(SikleState* ss)
{
  void* ptr;

  ptr = *(void**)(ES(ss)->sp - sizeof(void*));

  return ptr;
}

static inline 
void push_ptr(SikleState* ss, void* ptr)
{
  *(void**)(ES(ss)->sp) = ptr;
  ES(ss)->sp += sizeof(void*);
}

static inline
void* pop_ptr(SikleState* ss)
{
  void* ptr;

  ES(ss)->sp -= sizeof(void*);
  ptr = *(void**)(ES(ss)->sp);

  return ptr;
}

static inline 
NString peek_string(SikleState* ss)
{
  NString ns;

  ns.size = pop_integer(ss);
  ns.data = (char*)ES(ss)->sp - ns.size;
  push_integer(ss, ns.size);

  return ns;
}

static inline
void push_string(SikleState* ss, NString ns)
{
  int i;
  for(i = 0; i < ns.size; i++) {
    *(char*)ES(ss)->sp = ns.data[i];
    ES(ss)->sp++;
  }
  push_integer(ss, ns.size);
}

static inline 
NString pop_string(SikleState* ss)
{
  int i;
  NString ns;

  ns.size = pop_integer(ss);
  ns.data = malloc(sizeof(ns.size));
  for(i = ns.size-1; i >= 0; i--) {
    ES(ss)->sp--;
    ns.data[i] = *ES(ss)->sp;
  }

  return ns;
}

static inline 
void push_token(ProgramStack* ps, Token tok)
{
  *(ps->sp) = tok;
  ps->sp++;
}

/* builtin.c */
void register_builtin_word(SikleState* ss, char* name, builtin_word_func bwf);
void register_builtin_words(SikleState* ss);

/* sikle.c */
void call_word(SikleState* ss, Word* word);
